home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 2000 August: Tool Chest / Dev.CD Aug 00 TC Disk 1.toast / pc / sample code / contributed / waste / waste 1.3 / source / weselecting.c < prev    next >
Encoding:
C/C++ Source or Header  |  2000-06-23  |  58.3 KB  |  2,326 lines

  1. /*
  2.  *    WESelecting.c
  3.  *
  4.  *    WASTE PROJECT
  5.  *  Drawing Selections, Activating, Updating, Scrolling, etc.
  6.  *
  7.  *  Copyright (c) 1993-1998 Marco Piovanelli
  8.  *    All Rights Reserved
  9.  *
  10.  *  C port by Dan Crevier
  11.  *
  12.  */
  13.  
  14.  
  15. #include "WASTEIntf.h"
  16.  
  17. // values for _WEArrowOffset action parameter:
  18.  
  19. enum
  20. {
  21.     // plain arrow keys
  22.     kGoLeft            =    0,
  23.     kGoRight        =    1,
  24.     kGoUp            =    2,
  25.     kGoDown            =    3,
  26.  
  27.     // modifiers
  28.     kOption            =    4,
  29.     kCommand        =    8,
  30.  
  31.     // option + arrow combos
  32.     kGoWordStart    =    kGoLeft + kOption,
  33.     kGoWordEnd        =    kGoRight + kOption,
  34.     kGoPageStart    =    kGoUp + kOption,
  35.     kGoPageEnd        =    kGoDown + kOption,
  36.  
  37.     // command + arrow combos
  38.     kGoLineStart    =    kGoLeft + kCommand,
  39.     kGoLineEnd        =    kGoRight + kCommand,
  40.     kGoTextStart    =    kGoUp + kCommand,
  41.     kGoTextEnd        =    kGoDown + kCommand
  42. };
  43.  
  44. INLINE pascal void _WEClearHiliteBit(void)
  45. {
  46.     LMSetHiliteMode(LMGetHiliteMode() & 0x7F);
  47. }
  48.  
  49. static Boolean SLPixelToChar
  50.     (
  51.         WELineRec *pLine,
  52.         const WERunAttributes *pAttrs,
  53.         Ptr pSegment,
  54.         SInt32 segmentStart,
  55.         SInt32 segmentLength,
  56.         JustStyleCode styleRunPosition,
  57.         WEHandle hWE,
  58.         void *callbackData
  59.     )
  60. {
  61. #if !WASTE_OBJECTS
  62.     #pragma unused(pAttrs)
  63. #endif
  64.     struct SLPixelToCharData *cd = (struct SLPixelToCharData *) callbackData;
  65.     WEPtr pWE = *hWE;
  66.     Fixed slop;
  67.     SInt16 cType;
  68.     Fixed oldWidth;
  69. #if WASTE_OBJECTS
  70.     Fixed objectWidth;
  71.     Fixed subWidth;
  72. #endif
  73.  
  74.     // if this is the first style run on the line, subtract pen indent from pixelWidth
  75.     if (IS_LEFTMOST_RUN(styleRunPosition))
  76.     {
  77.         cd->pixelWidth -= BSL(_WECalcPenIndent(pLine, pWE->alignment, pWE->direction), 16);
  78.     }
  79.  
  80.     // if pixelWidth is gone negative already, the point is on the
  81.     // _leading_ edge (NOTE: was trailing in WASTE 1.2) of first glyph
  82.     if (cd->pixelWidth <= 0)
  83.     {
  84.         cd->offset = segmentStart;
  85.         cd->edge = kLeadingEdge;
  86.         return true;    // stop looping
  87.     }
  88.  
  89.     oldWidth = cd->pixelWidth;
  90.  
  91. #if WASTE_OBJECTS
  92.     if (pAttrs->runStyle.tsObject != nil)
  93.     {
  94.  
  95.         // EMBEDDED OBJECT
  96.         // calculate object width as Fixed
  97.         objectWidth = BSL((*pAttrs->runStyle.tsObject)->objectSize.h, 16);
  98.  
  99.         // subtract object width from pixelWidth
  100.         cd->pixelWidth -= objectWidth;
  101.  
  102. #if WASTE_OBJECTS_ARE_GLYPHS
  103.  
  104.         // find out whether the point is in the leftmost half of the object,
  105.         // in the rightmost half or past the object
  106.         subWidth = objectWidth >> 1;    // divide by two
  107.         if (cd->pixelWidth + subWidth < 0)
  108.         {
  109.             cd->offset = segmentStart;
  110.             cd->edge = kLeadingEdge;        // point is in leftmost half of object
  111.         }
  112.         else
  113.         {
  114.             cd->offset= segmentStart + 1;
  115.             if (cd->pixelWidth < 0)
  116.             {
  117.                 cd->edge = kTrailingEdge;
  118.             }    // point is in rightmost half of object
  119.             else
  120.             {
  121.                 cd->edge = kLeadingEdge;    // point is past object
  122.             }
  123.         }
  124. #else
  125.  
  126.         // find out whether the point is in the leftmost quarter of the object,
  127.         // in the middle half, in the rightmost quarter or past the object
  128.         subWidth = objectWidth >> 2;    // divide by four
  129.         if (cd->pixelWidth + subWidth < 0)
  130.         {
  131.             cd->offset = segmentStart;
  132.             if (cd->pixelWidth + objectWidth < subWidth)
  133.             {
  134.                 cd->edge = kLeadingEdge;    // point is in leftmost quarter of object
  135.             }
  136.             else
  137.             {
  138.                 cd->edge = kObjectEdge;        // point is in middle half of object
  139.             }
  140.         }
  141.         else
  142.         {
  143.             cd->offset = segmentStart + 1;
  144.             if (cd->pixelWidth < 0)
  145.             {
  146.                 cd->edge = kTrailingEdge;    // point is in rightmost quarter of object
  147.             }
  148.             else
  149.             {
  150.                 cd->edge = kLeadingEdge;        // point is past object
  151.             }
  152.         }
  153. #endif // WASTE_OBJECTS_ARE_GLYPHS
  154.     }
  155.     else
  156. #endif // WASTE_OBJECTS
  157.     {
  158.  
  159.         // REGULAR TEXT
  160.  
  161.         // if this is the last segment on the line, strip the last blank character (if any),
  162.         // unless it is the last non-CR character in the whole text stream
  163.         if (IS_RIGHTMOST_RUN(styleRunPosition))
  164.         {
  165.             if ((segmentStart + segmentLength < pWE->textLength) ||
  166.                 pSegment[segmentLength - 1] == kEOL)
  167.             {
  168.                 cType = CallWECharTypeProc(pSegment, segmentLength - 1, FontScript(),
  169.                      hWE, pWE->charTypeHook);
  170.                 if ((cType & (smcTypeMask + smcClassMask)) == smCharPunct + smPunctBlank)
  171.                 {
  172.                     segmentLength -= ((cType & smcDoubleMask) ? 2 : 1);
  173.                 }
  174.             }
  175.         }
  176.  
  177.         // calculate slop for this text segment (justified text only)
  178.         if (pWE->alignment == weJustify)
  179.         {
  180.             slop = FixMul(PortionLine(pSegment, segmentLength, styleRunPosition,
  181.                     kOneToOneScaling, kOneToOneScaling), pLine->lineJustAmount);
  182.         }
  183.         else
  184.         {
  185.             slop = 0;
  186.         }
  187.  
  188.         // call PixelToChar hook for this segment
  189.         cd->offset = segmentStart + CallWEPixelToCharProc(pSegment, segmentLength,
  190.                 slop, &cd->pixelWidth, &cd->edge, styleRunPosition, cd->hPos,
  191.                 hWE, pWE->pixelToCharHook);
  192.     }
  193.  
  194.     // increment hPos by change in pixelWidth
  195.     cd->hPos += (oldWidth - cd->pixelWidth);
  196.  
  197.     // if pixelWidth has gone negative, we're finished; otherwise go to next run
  198.     return (cd->pixelWidth <= 0);
  199. }
  200.  
  201. pascal SInt32 WEGetOffset(const LongPt *thePoint, WEEdge *edge, WEHandle hWE)
  202. {
  203.     // given a long point in local coordinates,
  204.     // find the text offset corresponding to the nearest glyph
  205.  
  206.     WEPtr pWE;
  207.     SInt32 lineIndex;
  208.     LongPt tempPoint;
  209.     struct SLPixelToCharData cd;
  210.     Boolean saveWELock;
  211.  
  212.     // lock the WE record
  213.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  214.     pWE = *hWE;
  215.  
  216.     // tempPoint is thePoint, relative to the top left corner of the dest rect
  217.     tempPoint.v = thePoint->v - pWE->destRect.top;
  218.     tempPoint.h = thePoint->h - pWE->destRect.left;
  219.  
  220.     // if the point is above the destination rect, return zero
  221.     if (tempPoint.v < 0)
  222.     {
  223.         cd.offset = 0;
  224.         cd.edge = kTrailingEdge;
  225.     }
  226.     else
  227.     {
  228.         // if the point is below the last line, return last char offset
  229.         if (tempPoint.v >= WEGetHeight(0, LONG_MAX, hWE))
  230.         {
  231.             cd.offset = pWE->textLength;
  232.             cd.edge = kLeadingEdge;
  233.         }
  234.         else
  235.         {
  236.             // find the line index corresponding to the vertical pixel offset
  237.             lineIndex = _WEPixelToLine(tempPoint.v, hWE);
  238.  
  239.             // express the horizontal pixel offset as a Fixed value
  240.             cd.pixelWidth = BSL(tempPoint.h, 16);
  241.  
  242.             // walk through the segments on this line calling PixelToChar
  243.             cd.hPos = 0;
  244.             cd.offset = 0;
  245.             _WESegmentLoop(lineIndex, lineIndex, SLPixelToChar, &cd, hWE);
  246.         }
  247.     }
  248.  
  249.     // unlock the WE record
  250.     _WESetHandleLock((Handle) hWE, saveWELock);
  251.  
  252.     // return offset/edge pair
  253.     if (edge != nil)
  254.     {
  255.         *edge = cd.edge;
  256.     }
  257.     return cd.offset;
  258. }
  259.  
  260. static Boolean SLCharToPixel
  261.     (
  262.         WELineRec *pLine,
  263.         const WERunAttributes *pAttrs,
  264.         Ptr pSegment,
  265.         SInt32 segmentStart,
  266.         SInt32 segmentLength,
  267.         JustStyleCode styleRunPosition,
  268.         WEHandle hWE,
  269.         void *callbackData
  270.     )
  271. {
  272.     struct SLCharToPixelData *cd = (struct SLCharToPixelData *) callbackData;
  273.     WEPtr pWE = *hWE;
  274.     SInt32 offset;
  275.     Fixed slop;
  276.     SInt16 direction = cd->direction;
  277.     SInt16 width;
  278.     Boolean isInSegment;
  279.  
  280.     // calculate offset relative to beginning of segment
  281.     offset = cd->offset - segmentStart;
  282.  
  283.     // is offset within this segment?
  284.     isInSegment = ((offset >= 0) && (offset < segmentLength));
  285.  
  286.     // if this is the first style run on the line, add pen indent to thePoint.h
  287.     if (IS_LEFTMOST_RUN(styleRunPosition))
  288.     {
  289.         cd->thePoint->h += _WECalcPenIndent(pLine, pWE->alignment, pWE->direction);
  290.     }
  291.  
  292. #if WASTE_OBJECTS
  293.     if (pAttrs->runStyle.tsObject != nil)
  294.     {
  295.  
  296.         // EMBEDDED OBJECT
  297.         width = isInSegment ? 0 : (*pAttrs->runStyle.tsObject)->objectSize.h;
  298.     }
  299.     else
  300. #endif
  301.     {
  302.         // REGULAR TEXT
  303.         slop = 0;
  304.  
  305.         // calculate slop for this text segment (justified text only)
  306.         if (pWE->alignment == weJustify)
  307.         {
  308.             slop = FixMul(PortionLine(pSegment, segmentLength, styleRunPosition,
  309.                 kOneToOneScaling, kOneToOneScaling), pLine->lineJustAmount);
  310.         }
  311.  
  312.         if (BTST(pWE->flags, weFBidirectional))
  313.         {
  314.             //    SPECIAL TREATMENT FOR BIDIRECTIONAL SCRIPTS
  315.             Boolean    segmentDir = (BTST(pAttrs->runStyle.tsFlags, tsRightToLeft) != 0);
  316.  
  317.             if ((offset < 0) || (offset > segmentLength))
  318.             {
  319.                 // we want the total width of this segment, so set offset and direction appropriately
  320.                 if (segmentDir)
  321.                 {
  322.                     offset = 0;
  323.                     direction = rightCaret;
  324.                 }
  325.                 else
  326.                 {
  327.                     offset = segmentLength;
  328.                     direction = leftCaret;
  329.                 }
  330.             }
  331.             else if (cd->rightEdge)
  332.             {
  333.                 // we're drawing a cursor at the right edge of the line, so ensure we measure this whole segment
  334.                 isInSegment = false;
  335.                 if (cd->lineDir && !segmentDir)
  336.                 {
  337.                     offset = segmentLength;
  338.                     direction = leftCaret;
  339.                 }
  340.                 else if (!cd->lineDir && segmentDir)
  341.                 {
  342.                     offset = 0;
  343.                     direction = rightCaret;
  344.                 }
  345.             }
  346.             else if ((offset == 0) || (offset == segmentLength))
  347.             {
  348.                 isInSegment = false;
  349.                 // we're at a style run boundary
  350.                 if (((direction == leftCaret) && (! segmentDir)) || ((direction == rightCaret) && segmentDir))
  351.                 {
  352.                     // requested caret direction matches style run direction, so we can exit the loop
  353.                     isInSegment = true;
  354.                 }
  355.                 else
  356.                 {
  357.                     // need caret for direction opposite to this run, so need to check for direction run boundary;
  358.                     // determine whether preceding and following runs are R-L
  359.                     Boolean prevDir;
  360.                     Boolean nextDir;
  361.  
  362.                     //    note that if segmentStart is 0, WEGetRunDirection
  363.                     //    will return the primary line direction
  364.                     prevDir = WEGetRunDirection(segmentStart - 1, hWE);
  365.  
  366.                     //    again, if segmentStart + segmentLength == pWE->textLength,
  367.                     //    WEGetRunDirection will do the Right Thing
  368.                     nextDir = WEGetRunDirection(segmentStart + segmentLength, hWE);
  369.  
  370.                     if ((offset == 0) && (prevDir != segmentDir))
  371.                     {
  372.                         if (cd->lineDir)
  373.                         {
  374.                             if (segmentDir)
  375.                             {
  376.                                 //offset = 0;    // already known to be zero
  377.                                 direction = rightCaret;
  378.                             }
  379.                             else
  380.                             {
  381.                                 offset = segmentLength;
  382.                                 direction = leftCaret;
  383.                             }
  384.                         }
  385.                     }
  386.                     else if ((offset == segmentLength) && (nextDir != segmentDir))
  387.                     {
  388.                         if (!cd->lineDir)
  389.                         {
  390.                             if (segmentDir)
  391.                             {
  392.                                 offset = 0;
  393.                                 direction = rightCaret;
  394.                             }
  395.                             else
  396.                             {
  397.                                 //offset = segmentLength;
  398.                                 direction = leftCaret;
  399.                             }
  400.                         }
  401.                     }
  402.                     else
  403.                     {
  404.                         // not a direction boundary, so force caret dir to match style run dir, and exit loop
  405.                         direction = segmentDir ? rightCaret : leftCaret;
  406.                         isInSegment = true;
  407.                     }
  408.                 }
  409.             }
  410.         } // end of special treatment for bidirectional scripts
  411.  
  412.         // call CharToPixel to get width of segment up to specified offset
  413.         width = CallWECharToPixelProc(pSegment, segmentLength,
  414.             slop, offset, direction, styleRunPosition, cd->thePoint->h,
  415.             hWE, pWE->charToPixelHook);
  416.     }
  417.  
  418.     // advance thePoint.h by the width of this segment
  419.     cd->thePoint->h += width;
  420.  
  421.     // drop out of loop when we reach offset
  422.     return isInSegment;
  423. }
  424.  
  425. pascal void WEGetPoint(SInt32 offset, SInt16 direction, LongPt *thePoint, SInt16 *lineHeight, WEHandle hWE)
  426. {
  427.     // given a byte offset into the text, find the corresponding glyph position
  428.     // this routine is useful for highlighting the text and for positioning the caret
  429.  
  430.     WEPtr pWE;
  431.     WELineRec *pLine;
  432.     SInt32 lineIndex;
  433.     SInt32 height;
  434.     struct SLCharToPixelData cd;
  435.     Boolean saveWELock;
  436.  
  437.     // lock the WE record
  438.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  439.     pWE = *hWE;
  440.  
  441.     // the base point is the top left corner of the destination rectangle
  442.     *thePoint = * (LongPt *) &pWE->destRect;
  443.  
  444.     // first of all find the line on which the glyph lies
  445.     lineIndex = WEOffsetToLine(offset, hWE);
  446.  
  447.     // calculate the vertical coordinate and the line height
  448.     pLine = *pWE->hLines + lineIndex;
  449.     thePoint->v += pLine->lineOrigin;
  450.     height = pLine[1].lineOrigin - pLine[0].lineOrigin;
  451.  
  452.     if ((offset == pWE->textLength) && (WEGetChar(offset - 1, hWE) == kEOL))
  453.     {
  454.         // SPECIAL CASE: if offset is past the last character and
  455.         // the last character is a carriage return, return a point below the last line
  456.         WELineRec dummyLine;
  457.  
  458.         dummyLine.lineSlop = pWE->destRect.right - pWE->destRect.left;
  459.         dummyLine.lineJustAmount = 0;
  460.  
  461.         thePoint->v += height;
  462.         thePoint->h += _WECalcPenIndent(&dummyLine, pWE->alignment, pWE->direction);
  463.     }
  464.     else
  465.     {
  466. // ••• new version of this: better boundary behavior
  467.         SInt32    lineStart, lineEnd;
  468.         Boolean    lineDir;
  469.  
  470.         // determine the dominant line direction
  471.         lineDir = IsRightToLeft(pWE->direction);
  472.  
  473.         // simplify direction so it's either leftCaret or rightCaret
  474.         if (direction == hilite)
  475.         {
  476.             direction = (lineDir ? rightCaret : leftCaret);
  477.         }
  478.  
  479.         // find ends of line so we can special-case them
  480.         WEGetLineRange(lineIndex, &lineStart, &lineEnd, hWE);
  481.         if (lineEnd < pWE->textLength || WEGetChar(lineEnd - 1, hWE) == kEOL)
  482.         {
  483.             --lineEnd;
  484.         }
  485.  
  486.         if ((offset == lineStart && !lineDir && direction == leftCaret)
  487.             || (offset == lineEnd && lineDir && direction == rightCaret)
  488.            )
  489.         {
  490.             // SPECIAL CASE: if caret should be at left end of line, don't call style run loop
  491.             thePoint->h += _WECalcPenIndent(pLine, pWE->alignment, pWE->direction);
  492.         }
  493.         else
  494.         {
  495.             cd.lineDir = lineDir;
  496.             // Inform SLCharToPixel if we want the right edge of the line
  497.             cd.rightEdge = ((offset == lineEnd && !lineDir && direction == leftCaret) ||
  498.                             (offset == lineStart && lineDir && direction == rightCaret));
  499.             cd.offset = offset;
  500.             cd.thePoint = thePoint;
  501.             cd.direction = direction;
  502.  
  503.             // to get the horizontal coordinate, walk through the style runs on this line
  504.             _WESegmentLoop(lineIndex, lineIndex, SLCharToPixel, &cd, hWE);
  505.         }
  506.  
  507. // ••• replaced with code above
  508. /*
  509.         cd.offset = offset;
  510.         cd.thePoint = thePoint;
  511.         cd.direction = direction;
  512.  
  513.         // to get the horizontal coordinate, walk through the style runs on this line
  514.         _WESegmentLoop(lineIndex, lineIndex, SLCharToPixel, &cd, hWE);
  515. */
  516.     }
  517.  
  518.     // pin the horizontal coordinate to the destination rectangle
  519.     thePoint->h = _WEPinInRange(thePoint->h, pWE->destRect.left, pWE->destRect.right);
  520.  
  521.     // unlock the WE record
  522.     _WESetHandleLock((Handle) hWE, saveWELock);
  523.  
  524.     // copy line height
  525.     if (lineHeight != nil)
  526.     {
  527.         *lineHeight = height;
  528.     }
  529. }
  530.  
  531. pascal void WEFindLine(SInt32 offset, WEEdge edge, SInt32 *lineStart, SInt32 *lineEnd, WEHandle hWE)
  532. {
  533. #pragma unused(edge)
  534.     WEPtr pWE = *hWE;
  535.     WELineRec *pLine;
  536.  
  537.     pLine = *pWE->hLines + WEOffsetToLine(offset, hWE);
  538.     if (lineStart != nil)
  539.     {
  540.         *lineStart = pLine[0].lineStart;
  541.     }
  542.     if (lineEnd != nil)
  543.     {
  544.         *lineEnd = pLine[1].lineStart;
  545.     }
  546. }
  547.  
  548. pascal void WEFindParagraph(SInt32 offset, WEEdge edge, SInt32 *paragraphStart, SInt32 *paragraphEnd, WEHandle hWE)
  549. {
  550. #pragma unused(edge)
  551.     WEPtr pWE = * hWE ;
  552.     Ptr pText = * pWE -> hText ;
  553.     SInt32 textLength = pWE -> textLength ;
  554.     WELineRec * pStartLine = * pWE -> hLines + WEOffsetToLine ( offset, hWE ) ;
  555.     WELineRec * pLine ;
  556.  
  557.     //    scan backwards the line array, looking for a line ending with a CR
  558.     for (
  559.             pLine = pStartLine ;
  560.             ( pLine -> lineStart > 0 ) && ( pText [ pLine -> lineStart - 1 ] != kEOL ) ;
  561.             pLine --
  562.         ) ;
  563.     if ( paragraphStart != nil )
  564.     {
  565.         * paragraphStart = pLine -> lineStart ;
  566.     }
  567.  
  568.     //    now scan forward
  569.     for (
  570.             pLine = pStartLine + 1 ;
  571.             ( pLine -> lineStart < textLength ) && ( pText [ pLine -> lineStart - 1 ] != kEOL ) ;
  572.             pLine ++
  573.         ) ;
  574.     if ( paragraphEnd != nil )
  575.     {
  576.         * paragraphEnd = pLine -> lineStart ;
  577.     }
  578. }
  579.  
  580. pascal ScriptCode _WEGetContext(SInt32 offset, SInt32 *contextStart, SInt32 *contextEnd,
  581.                         WEHandle hWE)
  582. {
  583.     // This function finds a range of characters ("context"), all belonging to the same script
  584.     // and centered around the specified offset.
  585.     // The function result is the ID of a font belonging to this script.
  586.     // Ideally, the context should consist of a whole script run, but in practice the returned
  587.     // context can be narrower, for performance and other reasons (see below)
  588.  
  589.     SInt32 index, saveIndex, saveRunEnd;
  590.     WERunInfo runInfo;
  591.     ScriptCode script1, script2;
  592.     SInt16 retval;
  593.  
  594.     if (BTST((*hWE)->flags, weFNonRoman))
  595.     {
  596.         // if more than one script is installed, limit the search of
  597.         // script run boundaries to a single paragraph, for speed's sake
  598.         WEFindParagraph(offset, kLeadingEdge, contextStart, contextEnd, hWE);
  599.  
  600.         // find the style run the specified offset is in
  601.         index = WEOffsetToRun(offset, hWE);
  602.         _WEGetIndStyle(index, &runInfo, hWE);
  603.  
  604.         // find the script code associated with this style run
  605.         script1 = FontToScript(runInfo.runAttrs.runStyle.tsFont);
  606.  
  607.         // the script code is returned as function result
  608.         retval = script1;
  609.  
  610.         // save index and runInfo.runEnd for the second while loop
  611.         saveIndex = index;
  612.         saveRunEnd = runInfo.runEnd;
  613.  
  614.         // walk backwards across style runs preceding offset, looking for a script run boundary
  615.         while (runInfo.runStart > *contextStart)
  616.         {
  617.             index--;
  618.             _WEGetIndStyle(index, &runInfo, hWE);
  619.             script2 = FontToScript(runInfo.runAttrs.runStyle.tsFont);
  620.             if (script1 != script2)
  621.             {
  622.                 *contextStart = runInfo.runEnd;
  623.                 break;
  624.             }
  625.         }
  626.  
  627.         // restore index and runInfo.runEnd
  628.         index = saveIndex;
  629.         runInfo.runEnd = saveRunEnd;
  630.  
  631.         // walk forward across style runs following offset, looking for a script run boundary
  632.         while (runInfo.runEnd < *contextEnd)
  633.         {
  634.             index++;
  635.             _WEGetIndStyle(index, &runInfo, hWE);
  636.             script2 = FontToScript(runInfo.runAttrs.runStyle.tsFont);
  637.             if (script1 != script2)
  638.             {
  639.                 *contextEnd = runInfo.runStart;
  640.                 break;
  641.             }
  642.         }
  643.     }
  644.     else
  645.     {
  646.         // only the Roman script is enabled: the whole text constitutes one script run
  647.         retval = smRoman;
  648.         *contextStart = 0;
  649.         *contextEnd = (*hWE)->textLength;
  650.     }
  651.  
  652.     // make sure the range identified by contextStart/contextEnd is contained within
  653.     // the 32K byte range centered around the specified offset
  654.     // the reason for this is that many Script Manager routines (e.g. FindWord and CharByte)
  655.     // only accept 16-bit offsets, rather than 32-bit offsets
  656.  
  657.     *contextStart = _WEPinInRange(*contextStart, offset - (SHRT_MAX / 2), offset);
  658.     *contextEnd = _WEPinInRange(*contextEnd, offset, offset + (SHRT_MAX / 2));
  659.  
  660.     return retval;
  661. }
  662.  
  663. pascal ScriptCode _WEGetRestrictedContext(SInt32 offset, SInt32 *contextStart, SInt32 *contextEnd,
  664.                         WEHandle hWE)
  665. {
  666.     // This function finds a range of characters ("context"), all belonging to the same script
  667.     // and centered around the specified offset.
  668.     // This function returns a script run subrange and is more efficient than
  669.     // _WEGetContext because it doesn't try to find the script boundaries accurately.
  670.  
  671.     WERunInfo runInfo;
  672.  
  673.     // just find the style run the specified offset is in
  674.     WEGetRunInfo(offset, &runInfo, hWE);
  675.     *contextStart = runInfo.runStart;
  676.     *contextEnd = runInfo.runEnd;
  677.  
  678.     // make sure the range identified by contextStart/contextEnd is contained within
  679.     // the 32K byte range centered around the specified offset
  680.     // the reason for this is that many Script Manager routines (e.g. FindWord and CharByte)
  681.     // only accept 16-bit offsets, rather than 32-bit offsets
  682.  
  683.     *contextStart = _WEPinInRange(*contextStart, offset - (SHRT_MAX / 2), offset);
  684.     *contextEnd = _WEPinInRange(*contextEnd, offset, offset + (SHRT_MAX / 2));
  685.  
  686.     return FontToScript(runInfo.runAttrs.runStyle.tsFont);
  687. }
  688.  
  689. pascal void WEFindWord(SInt32 offset, WEEdge edge, SInt32 *wordStart, SInt32 *wordEnd, WEHandle hWE)
  690. {
  691.     WEPtr pWE;
  692.     ScriptCode script;
  693.     SInt32 contextStart, contextEnd;
  694.     Handle hText;
  695.     OffsetTable wordBreaks;
  696.     Boolean saveTextLock, saveWELock;
  697.  
  698.     // lock the WE record
  699.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  700.     pWE = *hWE;
  701.  
  702.     // find a script context containing the specified offset
  703.     // (words cannot straddle script boundaries)
  704.     script = _WEGetContext(offset, &contextStart, &contextEnd, hWE);
  705.  
  706.     // lock the text
  707.     hText = pWE->hText;
  708.     saveTextLock = _WESetHandleLock(hText, true);
  709.  
  710.     // call the word break hook
  711.     CallWEWordBreakProc(*hText + contextStart, contextEnd - contextStart,
  712.         offset - contextStart, edge, wordBreaks, script, hWE, pWE->wordBreakHook);
  713.  
  714.     // unlock the text
  715.     _WESetHandleLock(hText, saveTextLock);
  716.  
  717.     // calculate wordStart and wordEnd relative to the beginning of the text
  718.     if (wordStart != nil)
  719.     {
  720.         *wordStart = contextStart + wordBreaks[0].offFirst;
  721.     }
  722.     if (wordEnd != nil)
  723.     {
  724.         *wordEnd = contextStart + wordBreaks[0].offSecond;
  725.     }
  726. }
  727.  
  728. pascal SInt16 WECharByte(SInt32 offset, WEHandle hWE)
  729. {
  730.     WEPtr pWE;
  731.     Handle hText;
  732.     SInt32 contextStart, contextEnd;
  733.     ScriptCode script;
  734.     Boolean saveWELock, saveTextLock;
  735.     SInt16 retval = smSingleByte;
  736.  
  737.     // lock the WE record
  738.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  739.     pWE = *hWE;
  740.  
  741.     // do nothing unless there is at least one double-byte script system installed
  742.     // and make sure offset is within allowed bounds
  743.     if (BTST(pWE->flags, weFDoubleByte))
  744.     {
  745.         if ((offset >= 0) && (offset < pWE->textLength))
  746.         {
  747.  
  748.             // find a script context containing the specified offset
  749.             script = _WEGetRestrictedContext(offset, &contextStart, &contextEnd, hWE);
  750.  
  751.             // lock the text
  752.             hText = pWE->hText;
  753.             saveTextLock = _WESetHandleLock(hText, true);
  754.  
  755.             // pass the CharByte hook a pointer to the beginning of the style run
  756.             retval = CallWECharByteProc(*hText + contextStart,
  757.                 offset - contextStart, script, hWE, pWE->charByteHook);
  758.  
  759.             // unlock the text
  760.             _WESetHandleLock(hText, saveTextLock);
  761.         }
  762.     }
  763.  
  764.     // unlock the WE record
  765.     _WESetHandleLock((Handle) hWE, saveWELock);
  766.  
  767.     return retval;
  768. }
  769.  
  770. pascal SInt16 WECharType(SInt32 offset, WEHandle hWE)
  771. {
  772.     WEPtr pWE;
  773.     Handle hText;
  774.     SInt32 contextStart, contextEnd;
  775.     ScriptCode script;
  776.     Boolean saveWELock, saveTextLock;
  777.     SInt16 retval = 0;
  778.  
  779.     // lock the WE record
  780.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  781.     pWE = *hWE;
  782.  
  783.     // make sure offset is within allowed bounds
  784.     if ((offset >= 0) && (offset < pWE->textLength))
  785.     {
  786.  
  787.         // find a script context containing the specified offset
  788.         script = _WEGetRestrictedContext(offset, &contextStart, &contextEnd, hWE);
  789.  
  790.         // lock the text
  791.         hText = pWE->hText;
  792.         saveTextLock = _WESetHandleLock(hText, true);
  793.  
  794.         // pass the CharType hook a pointer to the beginning of the style run
  795.         retval = CallWECharTypeProc(*hText + contextStart,
  796.             offset - contextStart, script, hWE, pWE->charTypeHook);
  797.  
  798.         // unlock the text
  799.         _WESetHandleLock(hText, saveTextLock);
  800.     }
  801.  
  802.     // unlock the WE record
  803.     _WESetHandleLock((Handle) hWE, saveWELock);
  804.  
  805.     return retval;
  806. }
  807.  
  808. pascal void _WEGetCaretRect(SInt32 offset, SInt16 direction, Rect *caretRect, WEHandle hWE)
  809. {
  810.     LongPt thePoint;
  811.     SInt16 caretHeight;
  812.  
  813.     WEGetPoint(offset, direction, &thePoint, &caretHeight, hWE);
  814.     WELongPointToPoint(&thePoint, (Point *) &caretRect->top);
  815.     if (caretRect->left > (*hWE)->destRect.left)
  816.     {
  817.         caretRect->left--;
  818.     }
  819.  
  820.     caretRect->bottom = caretRect->top + caretHeight;
  821.     caretRect->right = caretRect->left + kCaretWidth;
  822. }
  823.  
  824. pascal void _WEDrawCaret(SInt32 offset, SInt16 direction, Boolean useDualCaret, WEHandle hWE)
  825. {
  826.     WEPtr pWE = *hWE;    // assume WE record is already locked
  827.     Rect caretRect;
  828.     GrafPtr savePort;
  829.     RgnHandle saveClip, auxRgn;
  830.  
  831.     // set up the port
  832.     GetPort(&savePort);
  833.     SetPort(pWE->port);
  834.  
  835.     // save the clip region
  836.     saveClip = NewRgn();
  837.     GetClip(saveClip);
  838.  
  839.     // restrict the clip region to the view region
  840.     auxRgn = NewRgn();
  841.     SectRgn(saveClip, pWE->viewRgn, auxRgn);
  842.     SetClip(auxRgn);
  843.     DisposeRgn(auxRgn);
  844.  
  845.     // calculate caret rectangle for the primary caret
  846.     _WEGetCaretRect(offset, direction, &caretRect, hWE);
  847.  
  848.     // should we use a dual caret?
  849.     if (useDualCaret)
  850.     {
  851.         // draw the primary caret
  852.         caretRect.bottom = (caretRect.top + caretRect.bottom) >> 1;
  853.         InvertRect(&caretRect);
  854.  
  855.         // calculate the caret rectangle for the secondary caret
  856.         _WEGetCaretRect(offset, (leftCaret + rightCaret) - direction, &caretRect, hWE);
  857.         caretRect.top = (caretRect.top + caretRect.bottom) >> 1;
  858.     }
  859.  
  860.     // draw the caret (either a single caret, or the secondary caret)
  861.     InvertRect(&caretRect);
  862.  
  863.     // restore the clip region
  864.     SetClip(saveClip);
  865.     DisposeRgn(saveClip);
  866.  
  867.     // restore the port
  868.     SetPort(savePort);
  869. }
  870.  
  871. pascal void _WEBlinkCaret(WEHandle hWE)
  872. {
  873.     WEPtr pWE = *hWE;                // assume WE record is already locked
  874.     SInt16 direction = hilite;
  875.     Boolean useDualCaret = false;
  876.  
  877.     // do nothing if we're not active
  878.     if (!BTST(pWE->flags, weFActive))
  879.     {
  880.         return;
  881.     }
  882.  
  883. #if WASTE_NO_RO_CARET
  884.     if (BTST(pWE->features, weFReadOnly) && !BTST(pWE->flags, weFCaretVisible))
  885.     {
  886.         return;
  887.     }
  888. #endif
  889.  
  890.     if (BTST(pWE->flags, weFBidirectional))
  891.     {
  892.         // SPECIAL PROCESSING FOR BIDIRECTIONAL SCRIPTS
  893.         // check if we should use a dual caret
  894.         if (BTST(GetScriptManagerVariable(smGenFlags), smfDualCaret))
  895.         {
  896.             //    DUAL CARET
  897.             useDualCaret = true;
  898.  
  899.             //    primary caret position depends on primary line direction
  900.             direction = IsRightToLeft(pWE->direction) ? rightCaret : leftCaret;
  901.         }
  902.         else
  903.         {
  904.             //    SINGLE (JUMPING) CARET
  905.             //    caret position depends on keyscript direction
  906.             //    when erasing, though, use previously used direction
  907.             //    in case keyscript changed in the meantime
  908.             if (BTST(pWE->flags, weFCaretVisible))
  909.             {
  910.                 direction = BTST(pWE->flags, weFCaretRight) ? rightCaret : leftCaret;
  911.             }
  912.             else
  913.             {
  914.                 if (GetScriptVariable(GetScriptManagerVariable(smKeyScript), smScriptRight))
  915.                 {
  916.                     direction = rightCaret;
  917.                     BSET(pWE->flags, weFCaretRight);
  918.                 }
  919.                 else
  920.                 {
  921.                     direction = leftCaret;
  922.                     BCLR(pWE->flags, weFCaretRight);
  923.                 }
  924.             }
  925.         }
  926.     }
  927.  
  928.     // redraw the caret, in XOR mode
  929.     _WEDrawCaret(pWE->selStart, direction, useDualCaret, hWE);
  930.  
  931.     // keep track of the current caret visibility status
  932.     BCHG(pWE->flags, weFCaretVisible);    // invert flag
  933.  
  934.     // update caretTime
  935.     pWE->caretTime = TickCount();
  936. }
  937.  
  938. static Boolean SLCollectHiliteRgn
  939.     (
  940.         WELineRec *pLine,
  941.         const WERunAttributes *pAttrs,
  942.         Ptr pSegment,
  943.         SInt32 segmentStart,
  944.         SInt32 segmentLength,
  945.         JustStyleCode styleRunPosition,
  946.         WEHandle hWE,
  947.         void *callbackData
  948.     )
  949. // Get the hilite rgn for the current run, or the portion thereof within the hilite range;
  950. // this just does FrameRect for the relevant area(s) -- caller is assumed to have a region open
  951. {
  952.     WEPtr pWE = *hWE;
  953.     struct SLCollectHiliteRgnData *cd = (struct SLCollectHiliteRgnData *) callbackData;
  954.     SInt16 lineHeight;
  955.     SInt32 segmentEnd;
  956.     SInt32 firstOffset, secondOffset;
  957.     OffsetTable    offsets;
  958.     SInt16 i;
  959.     Rect r;
  960.     Fixed slop;
  961.     SInt16 width;
  962.     SInt32 start, end, lineOrigin;
  963.     WEDirection direction;
  964.     SInt32 offset;
  965.  
  966.     // determine the line direction
  967.     direction = pWE->direction;
  968.     if (direction == weDirDefault)
  969.     {
  970.         direction = GetSysDirection();
  971.     }
  972.  
  973.     // find the line origin & height
  974.     lineOrigin = pWE->destRect.top + pLine->lineOrigin;
  975.     lineHeight = pLine[1].lineOrigin - pLine[0].lineOrigin;
  976.  
  977.     // initialize cd->hPos on first segment of the line
  978.     if (IS_LEFTMOST_RUN(styleRunPosition))
  979.     {
  980.         cd->hPos = pWE->destRect.left + _WECalcPenIndent(pLine, pWE->alignment, direction);
  981.  
  982.         // if selection starts before beginning of line AND direction is LR,
  983.         // OR selection ends after end of line AND direction is RL,
  984.         // then we need to include the area to the left of the line in the hilite
  985.  
  986.         if ( ((cd->rangeStart < pLine[0].lineStart) && (direction == weDirLeftToRight))
  987.           || ((cd->rangeEnd >= pLine[1].lineStart) && (direction == weDirRightToLeft)) )
  988.         {
  989.             // frame the rect to the left of the actual text
  990.             SetRect(&r, pWE->destRect.left, lineOrigin, cd->hPos, lineOrigin + lineHeight);
  991.             FrameRect(&r);
  992.         }
  993.     }
  994.  
  995.     // calculate segment end
  996.     segmentEnd = segmentStart + segmentLength;
  997.  
  998. #if WASTE_OBJECTS
  999.     if (pAttrs->runStyle.tsObject != nil)
  1000.     {
  1001.         // EMBEDDED OBJECT
  1002.  
  1003.         // get object width
  1004.         width = (*pAttrs->runStyle.tsObject)->objectSize.h;
  1005.  
  1006.         // if this object is hilited, add its rect to the region
  1007.         if (cd->rangeStart <= segmentStart && cd->rangeEnd >= segmentEnd)
  1008.         {
  1009.             SetRect(&r, cd->hPos, lineOrigin, cd->hPos + width, lineOrigin + lineHeight);
  1010.             FrameRect(&r);
  1011.         }
  1012.     }
  1013.     else
  1014. #endif
  1015.     {
  1016.         // REGULAR TEXT
  1017.  
  1018.         // calculate slop value if justifying
  1019.         if (pWE->alignment == weJustify)
  1020.         {
  1021.             slop = FixMul(PortionLine(pSegment, segmentLength, styleRunPosition, kOneToOneScaling, kOneToOneScaling), pLine->lineJustAmount);
  1022.         }
  1023.         else
  1024.         {
  1025.             slop = 0;
  1026.         }
  1027.  
  1028.         // see whether this run includes any hilited text
  1029.         if (cd->rangeEnd > segmentStart && cd->rangeStart < segmentEnd)
  1030.         {
  1031.             if (segmentStart < cd->rangeStart)
  1032.             {
  1033.                 firstOffset = cd->rangeStart - segmentStart;
  1034.             }
  1035.             else
  1036.             {
  1037.                 firstOffset = 0;
  1038.             }
  1039.  
  1040.             if (segmentEnd >= cd->rangeEnd)
  1041.             {
  1042.                 secondOffset = cd->rangeEnd - segmentStart;
  1043.             }
  1044.             else
  1045.             {
  1046.                 secondOffset = segmentLength;
  1047.             }
  1048.  
  1049.             HiliteText(pSegment, segmentLength, firstOffset, secondOffset, offsets);
  1050.  
  1051.             for ( i = 0; i <= 2; ++i )
  1052.             {    // for each pair of offsets...
  1053.                 if (offsets[i].offFirst != offsets[i].offSecond)
  1054.                 {    // if offset pair is not empty...
  1055.                     // calculate horizontal coordinates of start and end of hilite area
  1056.                     start = CallWECharToPixelProc(pSegment, segmentLength,
  1057.                         slop, offsets[i].offFirst, hilite, styleRunPosition, cd->hPos,
  1058.                         hWE, pWE->charToPixelHook);
  1059.                     end = CallWECharToPixelProc(pSegment, segmentLength,
  1060.                         slop, offsets[i].offSecond, hilite, styleRunPosition, cd->hPos,
  1061.                         hWE, pWE->charToPixelHook);
  1062.                     // frame the rectangle to be hilited
  1063.                     SetRect(&r, cd->hPos + start, lineOrigin, cd->hPos + end, lineOrigin + lineHeight);
  1064.                     FrameRect(&r);
  1065.                 }    // if offset pair is not empty
  1066.             }    // for each pair of offsets
  1067.         }
  1068.  
  1069.         // calculate the width of this segment
  1070.         if (BTST(pAttrs->runStyle.tsFlags, tsRightToLeft) == 0)
  1071.         {
  1072.             i = leftCaret;
  1073.             offset = segmentLength;
  1074.         }
  1075.         else
  1076.         {
  1077.             i = rightCaret;
  1078.             offset = 0;
  1079.         }
  1080.         width = CallWECharToPixelProc(pSegment, segmentLength, slop,
  1081.             offset, i, styleRunPosition, cd->hPos, hWE, pWE->charToPixelHook);
  1082.     }
  1083.  
  1084.     // update cd->hPos to account for the width of the segment
  1085.     cd->hPos += width;
  1086.  
  1087.     if (IS_RIGHTMOST_RUN(styleRunPosition))
  1088.     {
  1089.         // if selection ends after end of line AND direction is LR,
  1090.         // OR selection begins before beginning of line AND direction is RL,
  1091.         // then we need to include the area to the right of the line in the hilite
  1092.  
  1093.         if ( (cd->rangeEnd >= pLine[1].lineStart && direction == weDirLeftToRight)
  1094.           || (cd->rangeStart < pLine->lineStart && direction == weDirRightToLeft) )
  1095.         {
  1096.             // frame the rect to the right of the text
  1097.             SetRect(&r, cd->hPos, lineOrigin, pWE->destRect.right, lineOrigin + lineHeight);
  1098.             FrameRect(&r);
  1099.         }
  1100.     }
  1101.  
  1102.     return false;    // don't break out of the segment loop
  1103. }
  1104.  
  1105. pascal RgnHandle WEGetHiliteRgn(SInt32 rangeStart, SInt32 rangeEnd, WEHandle hWE)
  1106. {
  1107.     // returns the hilite region corresponding to the specified range
  1108.     // the caller is responsible for disposing of the returned region
  1109.     // when it's finished with it
  1110.  
  1111.     WEPtr pWE;
  1112.     RgnHandle hiliteRgn;
  1113.     LongRect selRect;
  1114.     LongPt firstPoint, lastPoint;
  1115.     SInt32 lineIndex;
  1116.     SInt16 firstLineHeight, lastLineHeight;
  1117.     Rect r;
  1118.     GrafPtr savePort;
  1119.     Boolean saveWELock;
  1120.     Boolean bidirectional;
  1121.     struct SLCollectHiliteRgnData callbackData;
  1122.  
  1123.     // lock the WE record
  1124.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  1125.     pWE = *hWE;
  1126.  
  1127.     // set up the port
  1128.     GetPort(&savePort);
  1129.     SetPort(pWE->port);
  1130.  
  1131.     // make sure rangeStart comes before rangeEnd
  1132.     _WEReorder(&rangeStart, &rangeEnd);
  1133.  
  1134.     // calculate pixel location corresponding to rangeStart
  1135.     WEGetPoint(rangeStart, hilite, &firstPoint, &firstLineHeight, hWE);
  1136.  
  1137.     // calculate pixel location corresponding to rangeEnd
  1138.     WEGetPoint(rangeEnd, hilite, &lastPoint, &lastLineHeight, hWE);
  1139.  
  1140.     // open a region: rects to be hilited will be accumulated in this
  1141.     OpenRgn();
  1142.  
  1143.     // any bidirectional scripts installed?
  1144.     bidirectional = (BTST(pWE->flags, weFBidirectional) != 0);
  1145.     if (bidirectional)
  1146.     {
  1147.         callbackData.rangeStart = rangeStart;
  1148.         callbackData.rangeEnd = rangeEnd;
  1149.     }
  1150.  
  1151.     if (firstPoint.v == lastPoint.v)
  1152.     {
  1153.         // selection range encompasses only one line
  1154.         // for bidirectional text, we need to loop through the style runs
  1155.         if (bidirectional)
  1156.         {
  1157.             lineIndex = WEOffsetToLine(rangeStart, hWE);
  1158.             _WESegmentLoop(lineIndex, lineIndex, SLCollectHiliteRgn, &callbackData, hWE);
  1159.         }
  1160.         else
  1161.         {
  1162.             WESetLongRect(&selRect, firstPoint.h, firstPoint.v, lastPoint.h, lastPoint.v + lastLineHeight);
  1163.             WELongRectToRect(&selRect, &r);
  1164.             FrameRect(&r);
  1165.         }
  1166.     }
  1167.     else
  1168.     {
  1169.         // selection range encompasses more than one line
  1170.         // hilite the first line
  1171.         if (bidirectional)
  1172.         {
  1173.             lineIndex = WEOffsetToLine(rangeStart, hWE);
  1174.             _WESegmentLoop(lineIndex, lineIndex, SLCollectHiliteRgn, &callbackData, hWE);
  1175.         }
  1176.         else
  1177.         {
  1178.             WESetLongRect(&selRect, firstPoint.h, firstPoint.v, pWE->destRect.right, firstPoint.v + firstLineHeight);
  1179.             WELongRectToRect(&selRect, &r);
  1180.             FrameRect(&r);
  1181.         }
  1182.  
  1183.         // any lines between the first and the last one?
  1184.         if (firstPoint.v + firstLineHeight < lastPoint.v)
  1185.         {
  1186.             // hilite all the lines in-between
  1187.             WESetLongRect(&selRect, pWE->destRect.left, firstPoint.v + firstLineHeight, pWE->destRect.right, lastPoint.v);
  1188.             WELongRectToRect(&selRect, &r);
  1189.             FrameRect(&r);
  1190.         }
  1191.  
  1192.         // hilite the last line
  1193.         if (bidirectional)
  1194.         {
  1195.             // ??? shouldn't this be WEOffsetToLine(rangeEnd - 1, hWE) ??? -- marco
  1196.             // ••• yes, I think you're right, but I haven't tested it thoroughly -- jonathan
  1197.             lineIndex = WEOffsetToLine(rangeEnd, hWE);
  1198.  
  1199.             // skip this if it's an empty last line of the document
  1200.             if ((rangeEnd < pWE->textLength) || (WEGetChar(rangeEnd - 1, hWE) != kEOL))
  1201.             {
  1202.                 _WESegmentLoop(lineIndex, lineIndex, SLCollectHiliteRgn, &callbackData, hWE);
  1203.             }
  1204.         }
  1205.         else
  1206.         {
  1207.             WESetLongRect(&selRect, pWE->destRect.left, lastPoint.v, lastPoint.h, lastPoint.v + lastLineHeight);
  1208.             WELongRectToRect(&selRect, &r);
  1209.             FrameRect(&r);
  1210.         }
  1211.     }
  1212.  
  1213.     // copy the accumulated region into a new region
  1214.     hiliteRgn = NewRgn();
  1215.     CloseRgn(hiliteRgn);
  1216.  
  1217.     // restrict this region to the view region
  1218.     SectRgn(hiliteRgn, pWE->viewRgn, hiliteRgn);
  1219.  
  1220.     // restore the port
  1221.     SetPort(savePort);
  1222.  
  1223.     // unlock the WE record
  1224.     _WESetHandleLock((Handle) hWE, saveWELock);
  1225.  
  1226.     // return the hilite region
  1227.     return hiliteRgn;
  1228. }
  1229.  
  1230. pascal void _WEHiliteRange(SInt32 rangeStart, SInt32 rangeEnd, WEHandle hWE)
  1231. {
  1232.     WEPtr pWE;
  1233.     RgnHandle saveClip, auxRgn, hiliteRgn;
  1234.     PenState savePen;
  1235.     GrafPtr savePort;
  1236.  
  1237.     // the WE record must be already locked
  1238.     pWE = *hWE;
  1239.  
  1240.     // do nothing if the specified range is empty
  1241.     if (rangeStart == rangeEnd)
  1242.     {
  1243.         return;
  1244.     }
  1245.  
  1246.     // set up the port
  1247.     GetPort(&savePort);
  1248.     SetPort(pWE->port);
  1249.  
  1250.     // create auxiliary regions
  1251.     saveClip = NewRgn();
  1252.     auxRgn = NewRgn();
  1253.  
  1254.     // restrict the clip region to the view rectangle
  1255.     GetClip(saveClip);
  1256.     SectRgn(saveClip, pWE->viewRgn, auxRgn);
  1257.     SetClip(auxRgn);
  1258.  
  1259.     // get the hilite region corresponding to the specified range
  1260.     hiliteRgn = WEGetHiliteRgn(rangeStart, rangeEnd, hWE);
  1261.  
  1262.     // hilite the region or frame it, depending on the setting of the active flag
  1263.     if (BTST(pWE->flags, weFActive))
  1264.     {
  1265.         _WEClearHiliteBit();
  1266.         InvertRgn(hiliteRgn);
  1267.     }
  1268.     else if (BTST(pWE->features, weFOutlineHilite))
  1269.     {
  1270.         GetPenState(&savePen);
  1271.         PenNormal();
  1272.         PenMode(patXor);
  1273.         _WEClearHiliteBit();
  1274.         FrameRgn(hiliteRgn);
  1275.         SetPenState(&savePen);
  1276.     }
  1277.  
  1278.     // restore the clip region
  1279.     SetClip(saveClip);
  1280.  
  1281.     // dispose of all regions
  1282.     DisposeRgn(saveClip);
  1283.     DisposeRgn(auxRgn);
  1284.     DisposeRgn(hiliteRgn);
  1285.  
  1286.     // restore the port
  1287.     SetPort(savePort);
  1288. }
  1289.  
  1290. pascal void WESetSelection(SInt32 selStart, SInt32 selEnd, WEHandle hWE)
  1291. {
  1292.     WEPtr pWE;
  1293.     SInt32 oldSelStart, oldSelEnd;
  1294.     Boolean saveWELock;
  1295.  
  1296.     // lock the WE record
  1297.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  1298.     pWE = *hWE;
  1299.  
  1300.     // range-check parameters
  1301.     selStart = _WEPinInRange(selStart, 0, pWE->textLength);
  1302.     selEnd = _WEPinInRange(selEnd, 0, pWE->textLength);
  1303.  
  1304.     // set the weFAnchorIsEnd bit if selStart > selEnd,  reorder the endpoints
  1305.     if (selStart > selEnd)
  1306.     {
  1307.         BSET(pWE->flags, weFAnchorIsEnd);
  1308.         _WEReorder(&selStart, &selEnd);
  1309.     }
  1310.     else
  1311.     {
  1312.         BCLR(pWE->flags, weFAnchorIsEnd);
  1313.     }
  1314.  
  1315.     // get old selection range
  1316.     oldSelStart = pWE->selStart;
  1317.     oldSelEnd = pWE->selEnd;
  1318.  
  1319.     // selection changed?
  1320.     if ((oldSelStart != selStart) || (oldSelEnd != selEnd))
  1321.     {
  1322.         // invalid the null style
  1323.         BCLR(pWE->flags, weFUseNullStyle);
  1324.  
  1325.         // hide the caret if it's showing
  1326.         if (BTST(pWE->flags, weFCaretVisible))
  1327.         {
  1328.             _WEBlinkCaret(hWE);
  1329.         }
  1330.  
  1331.         // set new selection range
  1332.         pWE->selStart = selStart;
  1333.         pWE->selEnd = selEnd;
  1334.  
  1335.         // skip this section if either recalc or redraw has been inhibited
  1336.         if (! (pWE->features & ((1L << weFInhibitRecal) | (1L << weFInhibitRedraw))))
  1337.         {
  1338.             // if we're active, invert the exclusive-OR between the old range and the new range.
  1339.             // if we're inactive, this optimization can't be used because of outline highlighting.
  1340.             if (BTST(pWE->flags, weFActive))
  1341.             {
  1342.                 _WEReorder(&oldSelStart, &selStart);
  1343.                 _WEReorder(&oldSelEnd, &selEnd);
  1344.                 _WEReorder(&oldSelEnd, &selStart);
  1345.             }
  1346.  
  1347.             _WEHiliteRange(oldSelStart, oldSelEnd, hWE);
  1348.             _WEHiliteRange(selStart, selEnd, hWE);
  1349.  
  1350.             if (!BTST(pWE->flags, weFMouseTracking))
  1351.             {
  1352.                 // redraw the caret immediately, if the selection range is empty
  1353.                 if (pWE->selStart == pWE->selEnd)
  1354.                 {
  1355.                     _WEBlinkCaret(hWE);
  1356.                 }
  1357.  
  1358.                 // clear clickCount, unless we're tracking the mouse
  1359.                 pWE->clickCount = 0;
  1360.  
  1361.                 // scroll the selection into view, unless we're tracking the mouse
  1362.                 WESelView(hWE);
  1363.  
  1364.             }
  1365.         } // if redrawing not inhibited
  1366.     } // if selection changed
  1367.  
  1368.     // unlock the WE record
  1369.     _WESetHandleLock((Handle) hWE, saveWELock);
  1370. }
  1371.  
  1372. static Boolean SLCrossDirectionBoundary
  1373.     (
  1374.         WELineRec *pLine,
  1375.         const WERunAttributes *pAttrs,
  1376.         Ptr pSegment,
  1377.         SInt32 segmentStart,
  1378.         SInt32 segmentLength,
  1379.         JustStyleCode styleRunPosition,
  1380.         WEHandle hWE,
  1381.         void *callbackData
  1382.     )
  1383. {
  1384. #pragma unused(pLine, pAttrs, pSegment, hWE)
  1385.     struct SLCrossDirectionBoundaryData *cd = (struct SLCrossDirectionBoundaryData *) callbackData;
  1386.     Boolean isOldSegment = false;
  1387.  
  1388.     // have we reached the segment that contains the old offset?
  1389.     isOldSegment = (cd->oldOffset == segmentStart + segmentLength);
  1390.  
  1391.     if (isOldSegment)
  1392.     {
  1393.         if (cd->movingRight)
  1394.         {
  1395.             cd->isDone = true;    //    exit on next iteration
  1396.             return false;
  1397.         }
  1398.         else
  1399.         {
  1400.             if (IS_LEFTMOST_RUN(styleRunPosition))
  1401.             {
  1402.                 cd->newOffset = segmentStart;
  1403.             }
  1404.             return true;        //    exit now
  1405.         }
  1406.     }
  1407.  
  1408.     cd->newOffset = segmentStart + segmentLength;
  1409.  
  1410.     if ((! isOldSegment) && (cd->isDone))
  1411.     {
  1412.         //    we're in the segment to the right of old segment
  1413.         return true;
  1414.     }
  1415.  
  1416.     return false;    //    keep looping
  1417. }
  1418.  
  1419. pascal SInt32 _WECrossDirectionBoundary(SInt32 offset, Boolean movingRight, WEHandle hWE)
  1420. {
  1421.     struct SLCrossDirectionBoundaryData cd;
  1422.     SInt32 lineIndex = WEOffsetToLine(offset, hWE);
  1423.  
  1424.     cd.oldOffset = cd.newOffset = offset;
  1425.     cd.movingRight = movingRight;
  1426.     cd.isDone = false;
  1427.     _WESegmentLoop(lineIndex, lineIndex, SLCrossDirectionBoundary, &cd, hWE);
  1428.     return cd.newOffset;
  1429. }
  1430.  
  1431. pascal SInt32 _WEArrowOffset(SInt16 action, SInt32 offset, WEHandle hWE)
  1432. {
  1433.     // given an action code (corresponding to a modifiers + arrow key combo)
  1434.     // and an offset into the text, find the offset of the new caret position
  1435.  
  1436.     WEPtr pWE = *hWE;    //    assume the WE record is already locked
  1437.     LongPt thePoint;
  1438.     SInt32 rangeStart, rangeEnd;
  1439.     SInt32 oldOffset;
  1440.     SInt32 scrollAmount;
  1441.     SInt16 lineHeight;
  1442.     SInt16 arrow;
  1443.     Boolean prevDir, nextDir;
  1444.     SInt16 cType;
  1445.  
  1446.     //    extract arrow portion from action parameter
  1447.     arrow = action & 0x0003;
  1448.  
  1449.     if (BTST(pWE->flags, weFBidirectional) && (arrow <= kGoRight))
  1450.     {
  1451.         //    bidirectional pre-processing for left/right arrow keys
  1452.         prevDir = WEGetRunDirection(offset - 1, hWE);
  1453.         nextDir = WEGetRunDirection(offset, hWE);
  1454.  
  1455.         //    determine whether offset is at a direction boundary
  1456.         if (prevDir != nextDir)
  1457.         {
  1458.             //    offset at direction boundary:
  1459.             //    determine whether we should cross the boundary
  1460.             if (prevDir != (arrow != kGoLeft))
  1461.             {
  1462.                 offset = _WECrossDirectionBoundary(offset, (arrow != kGoLeft), hWE);
  1463.             }
  1464.         } // at direction boundary
  1465.  
  1466.         if ((arrow == kGoLeft) ? prevDir : nextDir)
  1467.         {
  1468.             //    insertion point is within a RL run: invert left/right keys
  1469.             arrow = (kGoLeft + kGoRight) - arrow;
  1470.             action = (action & ~0x0003) | arrow;
  1471.         }
  1472.     }    //    bidirectional pre-processing
  1473.  
  1474.     switch (action)
  1475.     {
  1476.         case kGoLeft:
  1477.         {
  1478.             if (offset > 0)
  1479.             {
  1480.                 offset--;
  1481.                 if (WECharByte(offset, hWE) != smSingleByte)
  1482.                 {
  1483.                     offset--;
  1484.                 }
  1485.             }
  1486.             break;
  1487.         }
  1488.  
  1489.         case kGoRight:
  1490.         {
  1491.             if (offset < pWE->textLength)
  1492.             {
  1493.                 if (WECharByte(offset, hWE) != smSingleByte)
  1494.                 {
  1495.                     offset++;
  1496.                 }
  1497.                 offset++;
  1498.             }
  1499.             break;
  1500.         }
  1501.  
  1502.         case kGoUp:
  1503.         {
  1504.             WEGetPoint(offset, hilite, &thePoint, nil, hWE);
  1505.             thePoint.v--;
  1506.             offset = WEGetOffset(&thePoint, nil, hWE);
  1507.             break;
  1508.         }
  1509.  
  1510.         case kGoDown:
  1511.         {
  1512.             WEGetPoint(offset, hilite, &thePoint, &lineHeight, hWE);
  1513.             thePoint.v += lineHeight;
  1514.             offset = WEGetOffset(&thePoint, nil, hWE);
  1515.             break;
  1516.         }
  1517.  
  1518.         case kGoWordStart:
  1519.         {
  1520.             while (true)
  1521.             {
  1522.                 WEFindWord(offset, kTrailingEdge, &rangeStart, &rangeEnd, hWE);
  1523.                 offset = rangeStart;
  1524.  
  1525.                 // If the found range is empty, get outta here.  (Most
  1526.                 // likely this means that we have reached the beginning
  1527.                 // of the text.)
  1528.                 if (rangeStart == rangeEnd)
  1529.                 {
  1530.                     break;
  1531.                 }
  1532.  
  1533.                 cType = WECharType(rangeStart, hWE);
  1534.  
  1535.                 // If the char is punctuation (other than a number),
  1536.                 // it's not really a word, so keep looping.  Otherwise
  1537.                 // we're done.
  1538.                 if (((cType & smcTypeMask) != smCharPunct) ||
  1539.                     ((cType & smcClassMask) == smPunctNumber))
  1540.                 {
  1541.                     break;
  1542.                 }
  1543.             }
  1544.             break;
  1545.         }
  1546.  
  1547.         case kGoWordEnd:
  1548.         {
  1549.             while (true)
  1550.             {
  1551.                 WEFindWord(offset, kLeadingEdge, &rangeStart, &rangeEnd, hWE);
  1552.                 offset = rangeEnd;
  1553.  
  1554.                 // If the found range is empty, get outta here.
  1555.                 // (Most likely this means that we have reached
  1556.                 // the end of the text.)
  1557.                 if (rangeStart == rangeEnd)
  1558.                 {
  1559.                     break;
  1560.                 }
  1561.  
  1562.                 // `rangeEnd - 1' may point in the middle of a two-byte
  1563.                 // character; that's ok, CharType can deal with that.
  1564.                 cType = WECharType(rangeEnd - 1, hWE);
  1565.  
  1566.                 // If the char is punctuation (other than a number),
  1567.                 // it's not really a word, so keep looping.  Otherwise
  1568.                 // we're done.
  1569.                 if (((cType & smcTypeMask) != smCharPunct) ||
  1570.                     ((cType & smcClassMask) == smPunctNumber))
  1571.                 {
  1572.                     break;
  1573.                 }
  1574.             }
  1575.             break;
  1576.         }
  1577.  
  1578.         case kGoLineStart:
  1579.         {
  1580.             WEFindLine(offset, kLeadingEdge, &rangeStart, &rangeEnd, hWE);
  1581.             offset = rangeStart;
  1582.             break;
  1583.         }
  1584.  
  1585.         case kGoLineEnd:
  1586.         {
  1587.             WEFindLine(offset, kTrailingEdge, &rangeStart, &rangeEnd, hWE);
  1588.             offset = rangeEnd;
  1589.             if (offset < pWE->textLength)
  1590.             {
  1591.                 offset--;
  1592.                 if (WECharByte(offset, hWE) != smSingleByte)
  1593.                 {
  1594.                     offset--;
  1595.                 }
  1596.             }
  1597.             break;
  1598.         }
  1599.  
  1600.         case kGoPageStart:
  1601.         {
  1602.             oldOffset = offset;
  1603.  
  1604.             //    get the offset corresponding to the top of the page
  1605.             thePoint.v = pWE->viewRect.top;
  1606.             thePoint.h = pWE->destRect.left;
  1607.             offset = WEGetOffset(&thePoint, nil, hWE);
  1608.  
  1609.             //    if the top line isn't completely visible, pick the line below
  1610.             WEGetPoint(offset, hilite, &thePoint, &lineHeight, hWE);
  1611.             if (thePoint.v < pWE->viewRect.top)
  1612.             {
  1613.                 thePoint.v += lineHeight;
  1614.                 offset = WEGetOffset(&thePoint, nil, hWE);
  1615.             }
  1616.  
  1617.             //    if we're already at the top of this page, scroll up a page
  1618.             if (offset == oldOffset)
  1619.             {
  1620.                 scrollAmount = pWE->viewRect.bottom - pWE->viewRect.top - lineHeight;
  1621.  
  1622.                 //    get the offset corresponding to the top of the previous page
  1623.                 thePoint.v = pWE->viewRect.top - scrollAmount;
  1624.                 thePoint.h = pWE->destRect.left;
  1625.                 offset = WEGetOffset(&thePoint, nil, hWE);
  1626.  
  1627.                 //    if the top line isn't completely visible, pick the line below
  1628.                 WEGetPoint(offset, hilite, &thePoint, &lineHeight, hWE);
  1629.                 if (thePoint.v < pWE->viewRect.top - scrollAmount)
  1630.                 {
  1631.                     thePoint.v += lineHeight;
  1632.                     scrollAmount = pWE->viewRect.top - thePoint.v;
  1633.                     offset = WEGetOffset(&thePoint, nil, hWE);
  1634.                 }
  1635.             }
  1636.             break;
  1637.         }
  1638.  
  1639.         case kGoPageEnd:
  1640.         {
  1641.             oldOffset = offset ;
  1642.  
  1643.             //    get the offset corresponding to the bottom of the page
  1644.             thePoint.v = pWE->viewRect.bottom;
  1645.             thePoint.h = pWE->destRect.left;
  1646.             offset = WEGetOffset(&thePoint, nil, hWE);
  1647.  
  1648.             //    if the bottom line isn't completely visible, pick the line above
  1649.             WEGetPoint(offset, hilite, &thePoint, &lineHeight, hWE);
  1650.             if (thePoint.v + lineHeight > pWE->viewRect.bottom)
  1651.             {
  1652.                 thePoint.v -= lineHeight;
  1653.                 offset = WEGetOffset(&thePoint, nil, hWE);
  1654.             }
  1655.  
  1656.             //    if we're already at the bottom of this page, scroll down a page
  1657.             if (offset == oldOffset)
  1658.             {
  1659.                 scrollAmount = pWE->viewRect.bottom - pWE->viewRect.top - lineHeight;
  1660.  
  1661.                 //    get the offset corresponding to the bottom of the next page
  1662.                 thePoint.v = pWE->viewRect.bottom + scrollAmount;
  1663.                 thePoint.h = pWE->destRect.left;
  1664.                 offset = WEGetOffset(&thePoint, nil, hWE);
  1665.  
  1666.                 //    if the bottom line isn't completely visible, pick the line above
  1667.                 WEGetPoint(offset, hilite, &thePoint, &lineHeight, hWE);
  1668.                 if (thePoint.v + lineHeight > pWE->viewRect.bottom + scrollAmount)
  1669.                 {
  1670.                     thePoint.v -= lineHeight;
  1671.                     scrollAmount = pWE->viewRect.top - thePoint.v;
  1672.                     offset = WEGetOffset(&thePoint, nil, hWE);
  1673.                 }
  1674.             }
  1675.             break;
  1676.         }
  1677.  
  1678.         case kGoTextStart:
  1679.         {
  1680.             offset = 0;
  1681.             break;
  1682.         }
  1683.  
  1684.         case kGoTextEnd:
  1685.         {
  1686.             offset = pWE->textLength;
  1687.             break;
  1688.         }
  1689.     }
  1690.  
  1691.     if (BTST(pWE->flags, weFBidirectional) && (arrow <= kGoRight))
  1692.     {
  1693.         //    bidirectional post-processing for left/right arrow keys
  1694.         prevDir = WEGetRunDirection(offset - 1, hWE);
  1695.         nextDir = WEGetRunDirection(offset, hWE);
  1696.  
  1697.         //    determine whether offset is at a direction boundary
  1698.         //    and whether we should cross it
  1699.         if ((prevDir != nextDir) && (prevDir != (arrow == kGoLeft)))
  1700.         {
  1701.             offset = _WECrossDirectionBoundary(offset, (arrow == kGoLeft), hWE);
  1702.         }
  1703.     } // bidirectional post-processing
  1704.  
  1705.     return offset;
  1706. }
  1707.  
  1708. pascal void _WEDoArrowKey (SInt16 arrow, EventModifiers modifiers, WEHandle hWE)
  1709. {
  1710.     // this routine is called by WEKey to handle arrow keys
  1711.  
  1712.     WEPtr pWE = *hWE;    // assume the WE record is already locked
  1713.     SInt16 action;
  1714.     SInt32 selStart, selEnd;
  1715.     SInt32 caretLoc, anchor;
  1716.  
  1717.     // calculate the "action" parameter for _WEArrowOffset from arrow and modifiers
  1718.     action = arrow - kArrowLeft;            // possible range: 0..3
  1719.     if (modifiers & optionKey)
  1720.     {
  1721.         action += kOption;
  1722.     }
  1723.     if (modifiers & cmdKey)
  1724.     {
  1725.         action += kCommand;
  1726.     }
  1727.  
  1728.     // get selection range
  1729.     selStart = pWE->selStart;
  1730.     selEnd = pWE->selEnd;
  1731.  
  1732.     if ((modifiers & shiftKey) == 0)
  1733.     {
  1734.         // if selection range isn't empty, collapse it to one of the endpoints
  1735.         if (selStart < selEnd)
  1736.         {
  1737.             if ((arrow == kArrowLeft) || (arrow == kArrowUp))
  1738.             {
  1739.                 caretLoc = selStart;
  1740.             }
  1741.             else
  1742.             {
  1743.                 caretLoc = selEnd;
  1744.             }
  1745.         }
  1746.         else
  1747.         {
  1748.             // otherwise move the insertion point
  1749.             caretLoc = _WEArrowOffset(action, selStart, hWE);
  1750.         }
  1751.  
  1752.         // set anchor to caretLoc, so new selection will be empty
  1753.         anchor = caretLoc;
  1754.     }
  1755.     else
  1756.     {
  1757.         // shift key was held down: extend the selection rather than replacing it
  1758.         // find out which selection boundary is the anchor and which is the free endpoint
  1759.         if (BTST(pWE->flags, weFAnchorIsEnd))
  1760.         {
  1761.             anchor = selEnd;
  1762.             caretLoc = selStart;
  1763.         }
  1764.         else
  1765.         {
  1766.             anchor = selStart;
  1767.             caretLoc = selEnd;
  1768.         }
  1769.  
  1770.         // move the free endpoint
  1771.         caretLoc = _WEArrowOffset(action, caretLoc, hWE);
  1772.     }
  1773.  
  1774.     // select the new selection
  1775.     WESetSelection(anchor, caretLoc, hWE);
  1776. }
  1777.  
  1778. pascal Boolean WEAdjustCursor(Point mouseLoc, RgnHandle mouseRgn, WEHandle hWE)
  1779. {
  1780.     // Call WEAdjustCursor to set the cursor shape when the mouse is in the view rectangle.
  1781.     // MouseRgn should be either a valid region handle or nil.
  1782.     // If mouseRgn is supplied (i.e., if it's not nil), it is intersected with a region
  1783.     // in global coordinates within which the cursor is to retain its shape.
  1784.     // WEAdjustCursor returns true if the cursor has been set.
  1785.     // Your application should set the cursor only if WEAdjustCursor returns false.
  1786.  
  1787.     WEPtr pWE;
  1788.     RgnHandle auxRgn;
  1789.     Point portDelta;
  1790.     GrafPtr savePort;
  1791.     CursHandle hCursor;
  1792.     SInt16 cursorID;
  1793.     Boolean saveWELock;
  1794.     Boolean adjustCursor;
  1795.  
  1796.     adjustCursor = false;
  1797.     cursorID = iBeamCursor;
  1798.  
  1799.     // lock the WE record
  1800.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  1801.     pWE = *hWE;
  1802.  
  1803.     // set up the port
  1804.     GetPort(&savePort);
  1805.     SetPort(pWE->port);
  1806.  
  1807.     // calculate delta between the local coordinate system and the global one
  1808.     portDelta.v = 0;
  1809.     portDelta.h = 0;
  1810.     LocalToGlobal(&portDelta);
  1811.  
  1812.     // calculate the visible portion of the view rectangle, in global coordinates
  1813.     auxRgn = NewRgn();
  1814.     CopyRgn(pWE->viewRgn, auxRgn);
  1815.     SectRgn(auxRgn, pWE->port->visRgn, auxRgn);
  1816.     OffsetRgn(auxRgn, portDelta.h, portDelta.v);
  1817.  
  1818.     if (PtInRgn(mouseLoc, auxRgn))
  1819.     {
  1820.         // mouse is within view rectangle: it's up to us to set the cursor
  1821.         adjustCursor = true;
  1822.  
  1823. #if WASTE_DRAG_AND_DROP
  1824.         // if drag-and-drop is enabled, see if the mouse is within current selection
  1825.         if (BTST(pWE->flags, weFHasDragManager) && BTST(pWE->features, weFDragAndDrop))
  1826.         {
  1827.             if (pWE->selStart < pWE->selEnd)
  1828.             {
  1829.                 RgnHandle hiliteRgn ;
  1830.  
  1831.                 // get current hilite region in global coordinates
  1832.                 hiliteRgn = WEGetHiliteRgn(pWE->selStart, pWE->selEnd, hWE);
  1833.                 OffsetRgn(hiliteRgn, portDelta.h, portDelta.v);
  1834.  
  1835.                 // if mouse is within selection, set cursor to an arrow, else to an I-beam
  1836.                 // (actually, we still use an I-beam if less than GetDblTime() ticks have
  1837.                 // elapsed since the last mouse click, so that the cursor doesn't turn into an
  1838.                 // arrow while triple-clicking + dragging a range of lines)
  1839.  
  1840.                 if (PtInRgn(mouseLoc, hiliteRgn) && ((TickCount() > pWE->clickTime + GetDblTime()) ||
  1841.                     (pWE->clickEdge == kObjectEdge)))
  1842.                 {
  1843.                     cursorID = 0;                // use arrow cursor
  1844.                     CopyRgn(hiliteRgn, auxRgn);
  1845.                 }
  1846.                 else
  1847.                 {
  1848.                     DiffRgn(auxRgn, hiliteRgn, auxRgn);
  1849.                 }
  1850.  
  1851.                 // dispose of the hilite region
  1852.                 DisposeRgn(hiliteRgn);
  1853.  
  1854.             } // if drag-and-drop is enabled
  1855.         }
  1856. #endif    // WASTE_DRAG_AND_DROP
  1857.  
  1858.         // set the cursor
  1859.         if (cursorID == 0)
  1860.         {
  1861.             SetCursor(&qd.arrow);
  1862.         }
  1863.         else
  1864.         {
  1865.             hCursor = GetCursor(cursorID);
  1866.             if ((hCursor != nil) && (*hCursor != nil))
  1867.             {
  1868.                 SetCursor(*hCursor);
  1869.             }
  1870.         }
  1871.  
  1872.         // set mouseRgn, if provided
  1873.         if (mouseRgn != nil)
  1874.         {
  1875.             SectRgn(mouseRgn, auxRgn, mouseRgn);
  1876.         }
  1877.     }
  1878.     else
  1879.     {
  1880.         // mouse is outside view rectangle: don't set the cursor; subtract viewRgn from mouseRgn
  1881.         if (mouseRgn != nil)
  1882.         {
  1883.             DiffRgn(mouseRgn, auxRgn, mouseRgn);
  1884.         }
  1885.     }
  1886.     // dispose of the temporary region
  1887.     DisposeRgn(auxRgn);
  1888.  
  1889.     // restore the port
  1890.     SetPort(savePort);
  1891.  
  1892.     // unlock the WE record
  1893.     _WESetHandleLock((Handle) hWE, saveWELock);
  1894.  
  1895.     return adjustCursor;
  1896. }
  1897.  
  1898. pascal void WEIdle(UInt32 *maxSleep, WEHandle hWE)
  1899. {
  1900.     WEPtr pWE;
  1901.     UInt32 currentTime, blinkTime, sleep;
  1902.     Boolean saveWELock;
  1903.  
  1904.     // lock the WE record
  1905.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  1906.     pWE = *hWE;
  1907.  
  1908. #if WASTE_DEBUG
  1909.         _WESanityCheck(hWE);
  1910. #endif
  1911.  
  1912.     // the caret blinks only if we're active and the selection point is empty
  1913.     if (BTST(pWE->flags, weFActive) && (pWE->selStart == pWE->selEnd))
  1914.     {
  1915.         // get current time
  1916.         currentTime = TickCount();
  1917.  
  1918.         // calculate when the caret should be blinked again
  1919.         blinkTime = pWE->caretTime + GetCaretTime();
  1920.  
  1921.         if (currentTime < blinkTime)
  1922.         {
  1923.             sleep = blinkTime - currentTime;
  1924.         }
  1925.         else
  1926.         {
  1927.             _WEBlinkCaret(hWE);
  1928.             sleep = GetCaretTime();
  1929.         }
  1930.     }
  1931.     else
  1932.     {
  1933.         // if we don't need to blink the caret, we can sleep forever
  1934.         sleep = LONG_MAX;
  1935.     }
  1936.  
  1937.     // return sleepTime to the caller if maxSleep isn't nil
  1938.     if (maxSleep != nil)
  1939.     {
  1940.         *maxSleep = sleep;
  1941.     }
  1942.  
  1943.     // unlock the WE record
  1944.     _WESetHandleLock((Handle) hWE, saveWELock);
  1945. }
  1946.  
  1947. pascal void WEUpdate(RgnHandle updateRgn, WEHandle hWE)
  1948. {
  1949.     WEPtr pWE;
  1950.     LongRect updateRect;
  1951.     Rect r;
  1952.     RgnHandle saveClip, auxRgn;
  1953.     GrafPtr savePort;
  1954.     Boolean saveWELock;
  1955.  
  1956.     // lock the WE record
  1957.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  1958.     pWE = *hWE;
  1959.  
  1960.     // set up the port
  1961.     GetPort(&savePort);
  1962.     SetPort(pWE->port);
  1963.  
  1964.     // save the clip region
  1965.     saveClip = NewRgn();
  1966.     GetClip(saveClip);
  1967.  
  1968.     // clip to the insersection between updateRgn and the view rectangle
  1969.     // (updateRgn may be nil; in this case, just clip to the view rectangle)
  1970.     auxRgn = NewRgn();
  1971.     SectRgn(saveClip, pWE->viewRgn, auxRgn);
  1972.     if (updateRgn != nil)
  1973.     {
  1974.         SectRgn(auxRgn, updateRgn, auxRgn);
  1975.     }
  1976.     SetClip(auxRgn);
  1977.  
  1978.     if (!EmptyRgn(auxRgn))
  1979.     {
  1980.         // calculate the rectangle to update
  1981.         r = (*auxRgn)->rgnBBox;
  1982.         WERectToLongRect(&r, &updateRect);
  1983.  
  1984.         // find out which lines need to be redrawn and draw them
  1985.         // if updateRgn is nil, erase each line rectangle before redrawing
  1986.         _WEDrawLines( _WEPixelToLine(updateRect.top - pWE->destRect.top, hWE),
  1987.                       _WEPixelToLine((updateRect.bottom - 1) - pWE->destRect.top, hWE),
  1988.                       (updateRgn == nil), hWE);
  1989.  
  1990.         // erase the portion of the update rectangle below the last line (if any)
  1991.         updateRect.top = pWE->destRect.top + (*pWE->hLines)[pWE->nLines].lineOrigin;
  1992.         if (updateRect.top < updateRect.bottom)
  1993.         {
  1994.             WELongRectToRect(&updateRect, &r);
  1995.             CallWEEraseProc(&r, hWE, pWE->eraseHook);
  1996.         }
  1997.  
  1998.         // hilite the selection range or draw the caret (only if active)
  1999.         if (pWE->selStart < pWE->selEnd)
  2000.         {
  2001.             _WEHiliteRange(pWE->selStart, pWE->selEnd, hWE);
  2002.         }
  2003.         else if (BTST(pWE->flags, weFCaretVisible))
  2004.         {
  2005.             _WEBlinkCaret(hWE);
  2006.             BSET(pWE->flags, weFCaretVisible);
  2007.         }
  2008.     }
  2009.  
  2010.     DisposeRgn(auxRgn);
  2011.  
  2012.     // restore the clip region
  2013.     SetClip(saveClip);
  2014.     DisposeRgn(saveClip);
  2015.  
  2016.     // restore the port
  2017.     SetPort(savePort);
  2018.  
  2019.     // unlock the WE record
  2020.     _WESetHandleLock((Handle) hWE, saveWELock);
  2021. }
  2022.  
  2023. pascal void WEDeactivate(WEHandle hWE)
  2024. {
  2025.     WEPtr pWE;
  2026.     Boolean saveWELock;
  2027.  
  2028.     // lock the WE record
  2029.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  2030.     pWE = *hWE;
  2031.  
  2032.     // do nothing if we are already inactive
  2033.     if (BTST(pWE->flags, weFActive))
  2034.     {
  2035.  
  2036.         // hide the selection range or the caret
  2037.         _WEHiliteRange(pWE->selStart, pWE->selEnd, hWE);
  2038.         if (BTST(pWE->flags, weFCaretVisible))
  2039.         {
  2040.             _WEBlinkCaret(hWE);
  2041.         }
  2042.  
  2043.         // clear the active flag
  2044.         BCLR(pWE->flags, weFActive);
  2045.  
  2046.         // frame the selection
  2047.         _WEHiliteRange(pWE->selStart, pWE->selEnd, hWE);
  2048.  
  2049.         // dispose of the offscreen graphics world, if any
  2050.         if (pWE->offscreenPort != nil)
  2051.         {
  2052.             DisposeGWorld((GWorldPtr)(pWE->offscreenPort));
  2053.             pWE->offscreenPort = nil;
  2054.         }
  2055.  
  2056. #if WASTE_TSM_SUPPORT
  2057.         // notify Text Services
  2058.         if (pWE->tsmReference != nil)
  2059.         {
  2060.             DeactivateTSMDocument(pWE->tsmReference);
  2061.         }
  2062. #endif
  2063.     }
  2064.  
  2065.     // unlock the WE record
  2066.     _WESetHandleLock((Handle) hWE, saveWELock);
  2067. }
  2068.  
  2069. pascal void WEActivate(WEHandle hWE)
  2070. {
  2071.     WEPtr pWE;
  2072.     Boolean saveWELock;
  2073.  
  2074.     if (WEIsActive(hWE)) return;
  2075.  
  2076.     // lock the WE record
  2077.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  2078.     pWE = *hWE;
  2079.  
  2080.     // remove the selection frame
  2081.     _WEHiliteRange(pWE->selStart, pWE->selEnd, hWE);
  2082.  
  2083.     // set the active flag
  2084.     BSET(pWE->flags, weFActive);
  2085.  
  2086.     // show the selection range
  2087.     _WEHiliteRange(pWE->selStart, pWE->selEnd, hWE);
  2088.  
  2089. #if WASTE_TSM_SUPPORT
  2090.     // notify Text Services
  2091.     if (pWE->tsmReference != nil)
  2092.     {
  2093.         ActivateTSMDocument(pWE->tsmReference);
  2094.     }
  2095. #endif
  2096.  
  2097.     // unlock the WE record
  2098.     _WESetHandleLock((Handle) hWE, saveWELock);
  2099. }
  2100.  
  2101. pascal Boolean WEIsActive(WEHandle hWE)
  2102. {
  2103.     // return true iff the specified WE instance is currently active
  2104.     return BTST((*hWE)->flags, weFActive) ? true : false;
  2105. }
  2106.  
  2107. pascal void WEScroll(SInt32 hOffset, SInt32 vOffset, WEHandle hWE)
  2108. {
  2109.     WEPtr pWE;
  2110.     Rect viewRect;
  2111.     GrafPtr savePort;
  2112.     Boolean hideOutline, saveWELock;
  2113.  
  2114.     // do nothing if both scroll offsets are zero
  2115.     if ((hOffset == 0) && (vOffset == 0))
  2116.     {
  2117.         return;
  2118.     }
  2119.  
  2120.     // lock the WE record
  2121.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  2122.     pWE = *hWE;
  2123.  
  2124.     // set up the port
  2125.     GetPort(&savePort);
  2126.     SetPort(pWE->port);
  2127.  
  2128.     // get view rect in short coordinates
  2129.     viewRect = (*pWE->viewRgn)->rgnBBox;
  2130.  
  2131.     // hide the caret if it's showing
  2132.     if (BTST(pWE->flags, weFCaretVisible))
  2133.     {
  2134.         _WEBlinkCaret(hWE);
  2135.     }
  2136.  
  2137.     // if we're inactive and outline highlighting is on, we have to temporarily
  2138.     // hide the selection outline while scrolling to avoid a cosmetic bug
  2139.     hideOutline = false;
  2140.     if (!BTST(pWE->flags, weFActive))
  2141.     {
  2142.         if (BTST(pWE->features, weFOutlineHilite))
  2143.         {
  2144.             hideOutline = true;
  2145.             _WEHiliteRange(pWE->selStart, pWE->selEnd, hWE);
  2146.             BCLR(pWE->features, weFOutlineHilite);
  2147.         }
  2148.     }
  2149.  
  2150.     // offset the destination rectangle by the specified amount
  2151.     WEOffsetLongRect(&pWE->destRect, hOffset, vOffset);
  2152.  
  2153.     // scroll the view rectangle
  2154.     // we use ScrollRect unless the whole text is to be redrawn anyway
  2155.     // notice that ScrollRect takes short (16-bit) offset parameters,
  2156.     // while WEScroll deals with long (32-bit) quantities
  2157.     if ((ABS(hOffset) < (viewRect.right - viewRect.left)) && (ABS(vOffset) < (viewRect.bottom - viewRect.top)))
  2158.     {
  2159.         RgnHandle updateRgn = NewRgn();
  2160.  
  2161.         // ScrollRect will set updateRgn to the region to redraw
  2162.         ScrollRect(&viewRect, (SInt16) hOffset, (SInt16) vOffset, updateRgn);
  2163.  
  2164.         // redraw the exposed region
  2165.         WEUpdate(updateRgn, hWE);
  2166.         DisposeRgn(updateRgn);
  2167.     }
  2168.     else
  2169.     {
  2170.         // redraw the whole text
  2171.         WEUpdate(nil, hWE);
  2172.     }
  2173.  
  2174.     // redraw the selection outline, if hidden
  2175.     if (hideOutline)
  2176.     {
  2177.         BSET(pWE->features, weFOutlineHilite);
  2178.         _WEHiliteRange(pWE->selStart, pWE->selEnd, hWE);
  2179.     }
  2180.  
  2181.     // restore the port
  2182.     SetPort(savePort);
  2183.  
  2184.     // unlock the WE record
  2185.     _WESetHandleLock((Handle) hWE, saveWELock);
  2186. }
  2187.  
  2188. pascal void WEPinScroll(SInt32 hOffset, SInt32 vOffset, WEHandle hWE)
  2189. {
  2190.     WEPtr pWE = *hWE;    // we ain't gonna move memory
  2191.     SInt32 delta;
  2192.  
  2193.     if (vOffset > 0)
  2194.     {
  2195.         delta = pWE->viewRect.top - pWE->destRect.top;
  2196.  
  2197.         // if top of the destRect would be moved below top of the viewRect,
  2198.         // pin it to top of the viewRect
  2199.         if (vOffset > delta)
  2200.         {
  2201.             vOffset = delta;
  2202.         }
  2203.     }
  2204.     else if (vOffset < 0)
  2205.     {
  2206.         delta = pWE->viewRect.bottom - pWE->destRect.bottom;
  2207.  
  2208.         // if bottom of the destRect would be moved above bottom of the viewRect,
  2209.         // pin it to bottom of viewRect
  2210.         if (vOffset < delta)
  2211.         {
  2212.             vOffset = delta;
  2213.         }
  2214.     }
  2215.  
  2216.     WEScroll(hOffset, vOffset, hWE);
  2217. }
  2218.  
  2219. pascal Boolean _WEScrollIntoView (SInt32 offset, WEHandle hWE)
  2220. {
  2221.     WEPtr pWE = *hWE;
  2222.     LongPt thePoint;
  2223.     SInt16 lineHeight;
  2224.     SInt32 hScroll, vScroll, temp;
  2225.     Boolean retval = false;
  2226.  
  2227.     // do nothing if automatic scrolling is disabled
  2228.     if (BTST(pWE->features, weFAutoScroll))
  2229.     {
  2230.  
  2231.         // find the selection point
  2232.         WEGetPoint(offset, hilite, &thePoint, &lineHeight, hWE);
  2233.  
  2234.         // assume no scrolling is needed
  2235.         vScroll = 0;
  2236.         hScroll = 0;
  2237.  
  2238.         // determine if we need to scroll up
  2239.         temp = pWE->viewRect.top - thePoint.v;
  2240.         if (temp > 0)
  2241.         {
  2242.             vScroll = temp;    // scroll up just enough to make the top line flush
  2243.         }
  2244.  
  2245.         //    determine if we need to scroll down
  2246.         temp = pWE->viewRect.bottom - (thePoint.v + lineHeight);
  2247.         if (temp < 0)
  2248.         {
  2249.             vScroll = temp;    // scroll down just enough to make the bottom line flush
  2250.         }
  2251.  
  2252.         if (vScroll != 0)
  2253.         {
  2254.             // we'd like to superimpose the bottom margins of the dest/view rects, if possible
  2255.             temp = pWE->viewRect.bottom - pWE->destRect.bottom;
  2256.             if (temp > vScroll)
  2257.             {
  2258.                 vScroll = temp;
  2259.             }
  2260.             // but we also have to make sure the dest top isn't scrolled below the view top
  2261.             temp = pWE->viewRect.top - pWE->destRect.top;
  2262.             if (temp < vScroll)
  2263.             {
  2264.                 vScroll = temp;
  2265.             }
  2266.         }
  2267.  
  2268.         // determine if we need to scroll horizontally
  2269.         if ((thePoint.h - 1 < pWE->viewRect.left) || (thePoint.h >= pWE->viewRect.right))
  2270.         {
  2271.             // calculate the amount of horizontal scrolling needed to center the selection into view
  2272.             hScroll = ((pWE->viewRect.left + pWE->viewRect.right) >> 1) - thePoint.h;
  2273.  
  2274.             // we'd like to superimpose the right margins of the dest/view rects, if possible
  2275.             temp = pWE->viewRect.right - pWE->destRect.right;
  2276.             if (temp > hScroll)
  2277.             {
  2278.                 hScroll = temp;
  2279.             }
  2280.  
  2281.             // but we also have to make sure the dest left isn't scrolled to the right of the view left
  2282.             temp = pWE->viewRect.left - pWE->destRect.left;
  2283.             if (temp < hScroll)
  2284.             {
  2285.                 hScroll = temp;
  2286.             }
  2287.         }
  2288.  
  2289.         // scroll the text if necessary
  2290.         if ((vScroll != 0) || (hScroll != 0))
  2291.         {
  2292.             retval = true;
  2293.             WEScroll(hScroll, vScroll, hWE);
  2294.             BSET(pWE->flags, weFDestRectChanged);
  2295.         }
  2296.     }
  2297.  
  2298.     // notify our client of changes to the destination rectangle
  2299.     if (BTST(pWE->flags, weFDestRectChanged))
  2300.     {
  2301.         if (pWE->scrollProc != nil)
  2302.         {
  2303.             CallWEScrollProc(hWE, pWE->scrollProc);
  2304.         }
  2305.         BCLR(pWE->flags, weFDestRectChanged);
  2306.     }
  2307.  
  2308.     return retval;
  2309. }
  2310.  
  2311. pascal void WESelView(WEHandle hWE)
  2312. {
  2313.     WEPtr pWE;
  2314.     Boolean saveWELock;
  2315.  
  2316.     // lock the WE record
  2317.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  2318.     pWE = *hWE;
  2319.  
  2320.     // scroll the free endpoint of the selection into view
  2321.     _WEScrollIntoView(BTST(pWE->flags, weFAnchorIsEnd) ? pWE->selStart : pWE->selEnd, hWE);
  2322.  
  2323.     // unlock the WE record
  2324.     _WESetHandleLock((Handle) hWE, saveWELock);
  2325. }
  2326.